#include "statsxx/machine_learning/NeuralNet.hpp"

// STL
#include <iostream>


//
// DESC: Determine the layer labels for each neuron, which descibes the propagation of signals.
//
// NOTES:
//     ! This routine is a modified version of the original  NEURAL_NET::forward_prop().
//
inline void NEURAL_NET::get_layer_neuron_idx()
{
    this->layer_neuron_idx.clear();

    // RESET THE NEURAL NETWORK ...
    // note: the memory MUST be cleared and the network MUST be deactivated before we try to propagate a signal below
    this->clear_memory();
    this->deactivate_network();

    for( auto &n : this->m_neurons )
    {
        n.m_output.insert(n.m_output.begin(), 0.0);
    }

    // GET STARTING "BOTTOM" (INPUT) NEURONS, AND ASSIGN SOME FICTICIOUS INPUT ...
    std::vector<int> bottom_neurons;

    for( auto n : this->m_inp_neurons )
    {
        bottom_neurons.push_back(n);
    }

    for( auto n : this->m_bias_neurons )
    {
        bottom_neurons.push_back(n);
    }

    for( auto &n : this->m_neurons )
    {
        for( auto l : this->m_links_in[n.m_ID] )
        {
            if( m_links[l].tdelay != 0 )
            {
                bottom_neurons.push_back(n.m_ID);
            }
        }
    }

    std::vector<double> input( this->m_inp_neurons.size(), 1.0 );
    this->load_input(input);

    this->find_source_neurons(bottom_neurons, true);
    layer_neuron_idx.push_back(bottom_neurons);

    this->activate_neurons(bottom_neurons, true);

    // ... AND THEN, WHILE THEY POINT TO OTHER NEURONS (NOT JUST OUTPUT), PROPAGATE UP ...
    while( this->neurons_pt_to_neurons(bottom_neurons, true) )
    {
        // PROPAGATE SIGNAL (INPUT) UPSTREAM, KEEPING A ``HIT LIST'' OF IDs OF ALL NEURONS HIT ...
        std::vector<int> ID_hit_list;
        for( auto n : bottom_neurons )
        {
            this->propagate_signal(m_neurons[n], ID_hit_list, true);
        }

        this->find_source_neurons(ID_hit_list, true);
        layer_neuron_idx.push_back(ID_hit_list);

        this->activate_neurons(ID_hit_list, true);

        bottom_neurons.clear();
        for( auto ID : ID_hit_list )
        {
            bottom_neurons.push_back(ID);
        }
    }

    // UPON EXITING THE WHILE LOOP, THE BOTTOM NEURONS POINT TO THE STILL INACTIVE OUTPUT NEURONS ...
    std::vector<int> ID_hit_list;
    for( auto n : bottom_neurons )
    {
        this->propagate_signal(this->m_neurons[n], ID_hit_list, true);
    }

    this->find_source_neurons(ID_hit_list, true);
    layer_neuron_idx.push_back(ID_hit_list);

    // ... NO NEED TO CONTINUE PAST THIS POINT NOW THAT ALL NEURONS ARE ASSIGNED
}
